﻿#region License
// Copyright 2007-2008 Rory Plaire (codekaizen@gmail.com)
/*
 * This license governs use of the accompanying software. If you use the software, you
 * accept this license. If you do not accept the license, do not use the software.
 * 
 * 1. Definitions
 * The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
 * same meaning here as under U.S. copyright law.
 * A "contribution" is the original software, or any additions or changes to the software.
 * A "contributor" is any person that distributes its contribution under this license.
 * "Licensed patents" are a contributor's patent claims that read directly on its contribution.
 * 
 * 2. Grant of Rights
 * (A) Copyright Grant- Subject to the terms of this license, including the license conditions 
 *     and limitations in section 3, each contributor grants you a non-exclusive, worldwide, 
 *     royalty-free copyright license to reproduce its contribution, prepare derivative works 
 *     of its contribution, and distribute its contribution or any derivative works that you create.
 * (B) Patent Grant- Subject to the terms of this license, including the license conditions and 
 *     limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free 
 *     license under its licensed patents to make, have made, use, sell, offer for sale, import, 
 *     and/or otherwise dispose of its contribution in the software or derivative works of the 
 *     contribution in the software.
 * 
 * 3. Conditions and Limitations
 * (A) No Trademark License- This license does not grant you rights to use any contributors' 
 *     name, logo, or trademarks.
 * (B) If you bring a patent claim against any contributor over patents that you claim are 
 *     infringed by the software, your patent license from such contributor to the software 
 *     ends automatically.
 * (C) If you distribute any portion of the software, you must retain all copyright, patent, 
 *     trademark, and attribution notices that are present in the software.
 * (D) If you distribute any portion of the software in source code form, you may do so only under 
 *     this license by including a complete copy of this license with your distribution. If you 
 *     distribute any portion of the software in compiled or object code form, you may only do so 
 *     under a license that complies with this license.
 * (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no 
 *     express warranties, guarantees or conditions. You may have additional consumer rights under 
 *     your local laws which this license cannot change. To the extent permitted under your local laws, 
 *     the contributors exclude the implied warranties of merchantability, fitness for a particular 
 *     purpose and non-infringement.
 * 
 */
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Threading;
using NPack.Interfaces;

namespace NPack
{

    internal class ManagedVectorPage<TComponent, TVector> : SynchronizedStorage,
                                                            IEnumerable<Int32>
        where TComponent : IEquatable<TComponent>, IComparable<TComponent>,
                           IComputable<TComponent>, IConvertible, IFormattable
        where TVector : IBufferedVector<TComponent, TVector>, IEquatable<TVector>,
                        IComparable<TVector>, IComputable<Double, TVector>
    {
        [Flags]
        internal enum ManagedVectorPageState
        {
            VectorCountLocked = 2,
            IsFull = 4
        }

        public const Int32 PageSizeBytes = 4096;
        private const Int32 CpuCacheFillSize = 128;
        private static readonly Int32 ComponentSize = Marshal.SizeOf(typeof(TComponent));
        private static readonly Int32 MinLineSize;
        //private static readonly Int32 LineIndexScale;
        //private static readonly Int32 LineMask;
        //private static readonly Int32 LineIndexMask;

        static ManagedVectorPage()
        {
            if (ComponentSize >> 8 > 0)
            {
                throw new InvalidOperationException("Component size must be 256 bytes or less.");
            }

            Int32 oneBitPosition = 0;

            for (Int32 i = 0; i < 8; i++)
            {
                if ((ComponentSize >> i & 0x1) != 0)
                {
                    if (oneBitPosition > 0)
                    {
                        throw new InvalidOperationException("Component size must be multiple of 2.");
                    }

                    oneBitPosition = i;
                }
            }

            // TODO: this doesn't handle Big Endian
            //LineIndexScale = oneBitPosition;
            //LineMask = 0x3 << LineIndexScale;
            //LineIndexMask = ~(0x7fffffff << LineIndexScale);
            MinLineSize = CpuCacheFillSize / ComponentSize;
        }

        // 15 * (12 [line header] + 256) + 12 [lines header] = 4032 bytes...
        private readonly TComponent[][] _lines;

        // ... + 30 bytes = 4062 bytes
        private readonly Int32 _lineSize;
        private readonly Int32 _lineCount;
        private readonly UInt16 _pageId;
        private readonly Int32 _pageVectorLength;
        private Int32 _nextLineIndex;
        private Int32 _nextVectorIndex;
        private Int32 _vectorCount;
        private readonly BitVector32.Section _vectorIndexSection;
        private readonly BitVector32.Section _lineIndexSection;


        internal ManagedVectorPage(UInt16 pageId,
                                   Int32 pageVectorLength)
        {
            _lineSize = Math.Max(pageVectorLength, MinLineSize);
            _lineCount = PageSizeBytes / _lineSize;

            _lines = new TComponent[_lineCount][];

            for (Int32 i = 0; i < _lineCount; i++)
            {
                _lines[i] = new TComponent[_lineSize];
            }

            _pageId = pageId;
            _pageVectorLength = pageVectorLength;

            _vectorIndexSection = BitVector32.CreateSection((Int16)(_lineSize / _pageVectorLength - 1));
            _lineIndexSection = BitVector32.CreateSection((Int16)(_lineCount - 1), _vectorIndexSection);
        }

        public Int32 PageVectorLength
        {
            get { return _pageVectorLength; }
        }

        public UInt16 PageId
        {
            get { return _pageId; }
        }

        public Boolean IsFull
        {
            get { return GetState((Int32)ManagedVectorPageState.IsFull); }
        }

        public Int32 Count
        {
            get
            {
                return _vectorCount;
            }
        }

        public TComponent this[Int32 vectorId, Int32 componentIndex]
        {
            get
            {
                if (componentIndex < 0)
                {
                    throw new ArgumentOutOfRangeException("componentIndex",
                                                          componentIndex,
                                                          "Component index must be greater than 0.");
                }

                if (componentIndex >= _pageVectorLength)
                {
                    String message = String.Format("Component index must be less than the page vector length: {0}.",
                                                   _pageVectorLength);

                    throw new ArgumentOutOfRangeException("componentIndex",
                                                          componentIndex,
                                                          message);
                }

                int index;
                TComponent[] line = GetLine(vectorId, out index);
                return line[index + componentIndex];
            }
        }

        public TComponent[] GetLine(Int32 vectorId, out Int32 vectorStartIndex)
        {
            BitVector32 id = new BitVector32(vectorId);

            Int32 lineIndex = id[_lineIndexSection];
            vectorStartIndex = id[_vectorIndexSection] * _pageVectorLength;

            return _lines[lineIndex];
        }

        // TODO: optimize the 2 component case if needed
        //public Int32 AddVector(TComponent v0, TComponent v1)
        //{
        //    throw new NotImplementedException();
        //}

        // TODO: optimize the 3 component case if needed
        //public Int32 AddVector(TComponent v0, TComponent v1, TComponent v2)
        //{
        //    throw new NotImplementedException();
        //}

        public Int32 AddVector(params TComponent[] components)
        {
            if (components.Length != _pageVectorLength)
            {
                String message = String.Format("Vector must have {0} components to exist on this page, but has {1}",
                                               _pageVectorLength,
                                               components.Length);

                throw new ArgumentException(message);
            }

            Int32 currentLineIndex;
            Int32 currentVectorIndex;

            try
            {
                WaitOnSetState((Int32)ManagedVectorPageState.VectorCountLocked);

                if (IsFull)
                {
                    throw pageIsFull();
                }

                currentLineIndex = _nextLineIndex;
                currentVectorIndex = Interlocked.Add(ref _nextVectorIndex, _pageVectorLength) - _pageVectorLength;

                // Handle the line index out of bounds by resetting to 0 
                // and incrementing the next line index
                if (currentVectorIndex + _pageVectorLength > _lineSize)
                {
                    Thread.VolatileWrite(ref _nextVectorIndex, _pageVectorLength);
                    currentLineIndex = Interlocked.Increment(ref _nextLineIndex);
                    currentVectorIndex = 0;
                }

                // Check if adding this vector and the next vector would cause a page overflow
                if (currentLineIndex + 1 >= _lineCount &&
                    currentVectorIndex + (_pageVectorLength * 2) > _lineSize)
                {
                    SetState((Int32)ManagedVectorPageState.IsFull);
                }

                Interlocked.Increment(ref _vectorCount);
            }
            finally
            {
                // unlock, since we have our indexes
                UnsetState((Int32)ManagedVectorPageState.VectorCountLocked);
            }

            TComponent[] line = _lines[currentLineIndex];

            for (Int32 i = 0; i < _pageVectorLength; i++)
            {
                line[currentVectorIndex + i] = components[i];
            }

            BitVector32 id = new BitVector32(0);
            id[_lineIndexSection] = currentLineIndex;
            id[_vectorIndexSection] = currentVectorIndex / _pageVectorLength;

            return id.Data; //currentLineIndex << (_lineIndexSection.Offset - 1) + currentVectorIndex / _pageVectorLength;
        }

        #region Implementation of IEnumerable

        public IEnumerator<Int32> GetEnumerator()
        {
            Int32 lineLimit = _lineSize / _pageVectorLength;

            Int32 count = 0;

            try
            {
                WaitOnSetState((Int32)ManagedVectorPageState.VectorCountLocked);

                for (Int32 lineIndex = 0; lineIndex < _lines.Length; lineIndex++)
                {
                    for (Int32 vectorStartIndex = 0; vectorStartIndex < lineLimit; vectorStartIndex++)
                    {
                        BitVector32 id = new BitVector32(0);
                        id[_lineIndexSection] = lineIndex;
                        id[_vectorIndexSection] = vectorStartIndex;
                        yield return id.Data;

                        if (++count == _vectorCount)
                        {
                            yield break;
                        }
                    }
                }
            }
            finally
            {
                UnsetState((Int32)ManagedVectorPageState.VectorCountLocked);
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region Private helper methods
        private static Exception pageIsFull()
        {
            return new InvalidOperationException("Page is full.");
        }
        #endregion
    }
}
